package com.xmgps.bdt.domain.bokun.prototol;

import com.xmgps.bdt.domain.bokun.exception.BadDataFrameException;
import com.xmgps.bdt.infrastructure.util.BcdUtil;
import com.xmgps.bdt.infrastructure.util.HexUtil;
import com.xmgps.bdt.infrastructure.util.XorUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.text.MessageFormat;
import java.util.List;

/**
 * @author wangzezhou
 * @date 2022/6/2
 */
@Slf4j
public class CMDDecoder extends ByteToMessageDecoder {

    private final static int FRAME_MAX_LENGTH = 1516;

    /** CMD 定界标识位 */
    private final static byte DELIMITER_TAG = 0x7E;
    private final static byte ESCAPE_BYTE_7E = 0x7E;
    private final static byte UNESCAPE_BYTE_7D = 0x7D;
    private final static byte ESCAPE_7D_SUFFIX = 0x01;
    private final static byte ESCAPE_7E_SUFFIX = 0x02;

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {

        int endTagIndex;
        do {
            try {
                endTagIndex = this.findIndexOfEndTag(in);
                if (endTagIndex > 0){
                    int readerIndex = in.readerIndex();

                    // 截前去标识位
                    ByteBuf dataFrame = in.slice(readerIndex + 1, endTagIndex - 1);
                    ByteBuf cmdData = ctx.alloc().buffer(dataFrame.readableBytes());
                    unescape(dataFrame, cmdData);

                    if (verityXorSum(cmdData)) {
                        out.add(CMD.readCreate(cmdData));
                    }

                    // 跳过指令帧的报文大小
                    in.skipBytes(endTagIndex - readerIndex + 1);
                }else{
                    break;
                }
            }catch (BadDataFrameException e){
                // 使输入流不可再流，并关闭连接。
                in.readerIndex(in.writerIndex());
                ctx.channel().close();
                InetSocketAddress socketAddress = (InetSocketAddress)ctx.channel().remoteAddress();
                log.error("异常：{}。非法数据报文，断开 {} 连接。",e.getMessage(),socketAddress.getHostString());
                break;
            }
        }while (true);
    }

    private int findIndexOfEndTag(ByteBuf in) throws BadDataFrameException {

        if (in.readableBytes() == 0){
            return -1;
        }

        int offsetIndex = in.readerIndex();
        byte beginTag = in.getByte(offsetIndex++);
        if(beginTag != DELIMITER_TAG){
            String errorMessage = MessageFormat.format("起始标识符({0})不正确",beginTag);
            throw new BadDataFrameException(errorMessage);
        }

        int writerIndex = in.writerIndex();
        byte endTag;
        int dataFrameSize = 1;
        for (; offsetIndex < writerIndex; ++offsetIndex, ++dataFrameSize){

            endTag = in.getByte(offsetIndex);
            if (endTag == DELIMITER_TAG){
                return offsetIndex;
            }

            if( dataFrameSize > FRAME_MAX_LENGTH){
                String errorMessage = MessageFormat.format("数据帧长度（{0}）超过最大长度({1})", dataFrameSize, FRAME_MAX_LENGTH);
                throw new BadDataFrameException(errorMessage);
            }
        }

        return -1;
    }

    /**
     * 异或校验
     */
    private boolean verityXorSum(ByteBuf in){
        int dataLen = in.readableBytes()-1;
        byte xorValue = XorUtil.calcXOR(in,dataLen);
        return xorValue == in.getByte(dataLen);
    }

    /**
     * 逆协议转义
     */
    private void unescape(ByteBuf in, ByteBuf out) {

        if (log.isDebugEnabled()){
            log.debug("报文转义前：" + HexUtil.encodeString(in,in.readableBytes()));
        }

        int unescapeByteIndex;
        byte tagByte;
        int srcIndex = 0;

        do {
            unescapeByteIndex = in.indexOf(srcIndex, in.writerIndex() - 1, UNESCAPE_BYTE_7D);
            if (unescapeByteIndex >= 0) {

                out.writeBytes(in, srcIndex, unescapeByteIndex - srcIndex);

                tagByte = in.getByte(++unescapeByteIndex);
                // 进行逆转义处理
                switch (tagByte) {
                    case ESCAPE_7E_SUFFIX:
                        out.writeByte(ESCAPE_BYTE_7E);
                        ++unescapeByteIndex;
                        break;

                    case ESCAPE_7D_SUFFIX:
                        out.writeByte(UNESCAPE_BYTE_7D);
                        ++unescapeByteIndex;
                        break;
                    default:
                        throw new RuntimeException("命令存在非法转义规则");
                }

                srcIndex = unescapeByteIndex;
            } else {
                out.writeBytes(in, srcIndex, in.readableBytes() - srcIndex);
                break;
            }

        } while (true);

        if (log.isDebugEnabled()){
            log.debug("报文转义后：" + HexUtil.encodeString(out,out.readableBytes()));
        }
    }

}
